package com.pig4cloud.pigx.contract.schedule;

import com.pig4cloud.pigx.common.customize.contants.MongoTableNameConstants;
import com.pig4cloud.pigx.common.customize.enums.CommonDictEnum;
import com.pig4cloud.pigx.common.customize.mongo.utils.MongoUtils;
import com.pig4cloud.pigx.common.customize.redis.utils.RedisUtils;
import com.pig4cloud.pigx.contract.constant.ContractKLineConstants;
import com.pig4cloud.pigx.contract.constant.ContractRedisLockConstants;
import com.pig4cloud.pigx.contract.entity.ContractKLine;
import com.pig4cloud.pigx.contract.enums.ContractOrderEnum;
import com.pig4cloud.pigx.contract.mapper.ContractSymbolMapper;
import com.pig4cloud.pigx.contract.mongo.entity.ContractSymbolMongo;
import com.pig4cloud.pigx.contract.utils.ContractUtils;
import com.pig4cloud.pigx.contract.vo.Contract24hTradeVo;
import com.pig4cloud.pigx.contract.vo.ContractPositionStatisticsVO;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.springframework.beans.BeanUtils;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;

/**
 * 实时计算币对的'最新成交价格'和'最近24h成交记录数据' 定时任务
 *
 */
@Slf4j
@Component
@EnableScheduling
@AllArgsConstructor
public class ContractSymbolTask {

    private final ContractSymbolMapper symbolMapper;
    private final MongoTemplate mongo;
    private final RedisUtils redisUtils;
    private final ContractUtils utils;

    /**
     * 更新币对涨跌幅
     */
    @Scheduled(fixedDelay = 1000)
    private void symbolMongoNewest() {
        // 竞争分布式锁
        RLock lock = redisUtils.getLock(ContractRedisLockConstants.SYMBOL_MONGO_NEWEST, null);
        if (Objects.isNull(lock)) {
            return;
        }

        try {
            List<ContractSymbolMongo> mongoList = mongo.find(new Query(), ContractSymbolMongo.class);
            for (ContractSymbolMongo timingVO : mongoList) {
                Query query = Query.query(Criteria.where("_id").is(timingVO.getId()));
                ContractSymbolMongo lineMongo = new ContractSymbolMongo();
                // 查询最新日线
                ContractKLine line = utils.selectCurrentKLine(timingVO.getName(), ContractKLineConstants.DAY_1.getType());
                if (line == null) {
                    continue;
                }
                BigDecimal openPrice = line.getOpenPrice() == null ? timingVO.getLastPrice() : line.getOpenPrice();
                int compare = openPrice.compareTo(timingVO.getLastPrice());
                if (compare > 0) {
                    // 计算跌幅
                    BigDecimal divide = (openPrice.subtract(timingVO.getLastPrice())).divide(openPrice, 8, RoundingMode.UP).multiply(new BigDecimal("100"));
                    lineMongo.setRiseAndFallRange(divide.negate());
                } else if (compare == 0) {
                    // 价格相同视为无涨跌
                    lineMongo.setRiseAndFallRange(new BigDecimal("0"));
                } else {
                    // 计算涨幅
                    BigDecimal divide = (timingVO.getLastPrice()
                            .subtract(openPrice))
                            .divide(openPrice, 8, RoundingMode.UP)
                            .multiply(new BigDecimal("100"));
                    lineMongo.setRiseAndFallRange(divide);
                }

                // 计算币对溢价指数
//			BigDecimal divide = (timingVO.getLastPrice().subtract(timingVO.getIndexPrice()))
//					.divide(timingVO.getIndexPrice(),8,BigDecimal.ROUND_HALF_UP);
                // 计算资金费率
//			BigDecimal clamp = clamp(divide, timingVO.getMinCapitalRate(), timingVO.getMaxCapitalRate());
//			lineMongo.setFundingRate(clamp);
                Update update = MongoUtils.getUpdate(lineMongo);
                mongo.updateMulti(query, update, ContractSymbolMongo.class);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            redisUtils.releaseLock(lock);
        }
        log.info("-----币对Mongo已更新-----");
    }

    /**
     * 24小时成交
     */
    @Scheduled(fixedDelay = 1000)
    private void symbolMongo24Trade() {
        // 竞争分布式锁
        RLock lock = redisUtils.getLock(ContractRedisLockConstants.SYMBOL_MONGO_24H_TRADE, null);
        if (Objects.isNull(lock)) {
            return;
        }

        try {
            List<ContractSymbolMongo> mongoList = mongo.find(
                    Query.query(Criteria.where("status").is(CommonDictEnum.Status.NORMAL.getValue())),
                    ContractSymbolMongo.class);
            mongoList.forEach(e -> {
                // 查询币对24小时成交记录
                String collectionName = e.getName() + "_newest_trade_24h";
                Aggregation aggregation = Aggregation.newAggregation(
                        Aggregation
                                // 根据币对id分组
                                .group("symbolId")
                                // 聚合函数字段
                                .first("price").as("price")
                                .max("price").as("maxPrice")
                                .first("price").as("price")
                                .min("price").as("minPrice")
                                .first("num").as("num")
                                .sum("num").as("sumNum")
                                .first("tradeValue").as("tradeValue")
                                .sum("tradeValue").as("sumPrice")
                                // 查询基础字段
                                .first("symbolId").as("symbolId")
                                .first("symbolName").as("symbolName")
                );
                AggregationResults<Contract24hTradeVo> aggregate =
                        mongo.aggregate(aggregation, collectionName, Contract24hTradeVo.class);
                List<Contract24hTradeVo> results = aggregate.getMappedResults();
                for (Contract24hTradeVo vo : results) {
                    Query query = Query.query(Criteria.where("_id").is(Long.valueOf(vo.getSymbolId())));
                    ContractSymbolMongo lineMongo = new ContractSymbolMongo();
                    BeanUtils.copyProperties(vo, lineMongo);
                    Update update = MongoUtils.getUpdate(lineMongo);
                    mongo.updateFirst(query, update, ContractSymbolMongo.class, MongoTableNameConstants.CONTRACT_SYMBOL);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            redisUtils.releaseLock(lock);
        }
    }

    /**
     * 清理过期的24小时成交记录
     */
    @Scheduled(fixedDelay = 10000)
    private void clearSymbolMongo24Trade() {
        List<ContractSymbolMongo> mongoList = mongo.find(
                Query.query(Criteria.where("status").is(CommonDictEnum.Status.NORMAL.getValue())),
                ContractSymbolMongo.class);
        mongoList.forEach(e -> {
            LocalDateTime time = LocalDateTime.now().minusDays(1);
            String collectionName = e.getName() + "_newest_trade_24h";
            mongo.remove(
                    Query.query(Criteria.where("createTime").lt(time)),
                    collectionName);
        });
    }

    /**
     * 更新 各币对的 多/空方 持仓量 持仓人数
     */
    @Scheduled(fixedDelay = 5000)
    private void toUpdatePosition() {
        List<ContractSymbolMongo> mongoList = mongo.find(new Query(), ContractSymbolMongo.class);
        for (ContractSymbolMongo symbolMongo : mongoList) {
            Update update = new Update();
            update.set("emptyNum", null);
            update.set("emptyCount", null);
            update.set("moreNum", null);
            update.set("moreCount", null);
            mongo.updateFirst(Query.query(Criteria.where("_id").is(symbolMongo.getId())),
                    update,
                    ContractSymbolMongo.class,
                    MongoTableNameConstants.CONTRACT_SYMBOL);
        }
        List<ContractPositionStatisticsVO> list = symbolMapper.selectPositionStatistics();
        for (ContractPositionStatisticsVO vo : list) {
            ContractSymbolMongo lineMongo = new ContractSymbolMongo();
            Query query = new Query();
            query.addCriteria(Criteria.where("_id").is(vo.getSymbolId()));
            if (vo.getSide().equals(ContractOrderEnum.Side.EMPTY.getValue())) {
                lineMongo.setEmptyCount(vo.getCount());
                lineMongo.setEmptyNum(vo.getNum());
            } else {
                lineMongo.setMoreCount(vo.getCount());
                lineMongo.setMoreNum(vo.getNum());
            }
            Update update = MongoUtils.getUpdate(lineMongo);
            mongo.updateMulti(query, update, ContractSymbolMongo.class);
        }
    }

    /**
     * 资金费率公式
     * I = (((ask + bid) / 2) - P0) / P0
     * R/fee = clamp(I,a,b)
     * I: 溢价指数
     * a: 资金费率下限
     * b: 资金费率上限
     * P0: 指数价
     * (ask + bid) / 2) = 最新价
     * Clamp(A,B,C): 当A在B,C范围内时，取A: 否则上限取B,下限取C(B>C)
     *
     * @param val I 溢价指数
     * @param min 资金费率下限
     * @param max 资金费率上限
     * @return 资金费率
     */
    public BigDecimal clamp(BigDecimal val, BigDecimal min, BigDecimal max) {
        return BigDecimal.valueOf(Math.max(min.floatValue(), Math.min(max.floatValue(), val.floatValue())));
    }
}
